80장. 최소 권한 설계
이 장에서 말하고자 하는 것
IAM Policy는 자유롭게 쓸 수 있다.
Effect: Allow
Action: "*"
Resource: "*"
이 두 줄이면 모든 게 동작한다.
하지만 운영 보안의 절반은 이 유혹을 이겨내는 일이다.
Principle of Least Privilege (최소 권한 원칙)
이 장은 그 설계 방법을 정리한다.
1. 최소 권한이란 무엇인가
어떤 주체가 자기 일을 하는 데 꼭 필요한 권한만 가진다
- 더 많은 권한은 사고를 키운다
- 더 적은 권한은 정상 작업을 막는다
균형이 어렵지만 방향은 명확하다.
기본은 “다 막혀 있고, 필요한 것만 연다”
2. 권한이 새어 나가는 흔한 자리
1. * Action / Resource
{ "Action": "s3:*", "Resource": "*" }
가장 빈번한 사고 원인.
2. 와이드한 Managed Policy
AdministratorAccess, PowerUserAccess 같은 광범위 정책을 컨테이너에 박는다.
3. “잠깐만” 으로 추가한 권한이 영구화된다
디버깅용으로 풀어준 권한이 그대로 운영에 남는다.
4. 모든 환경에 같은 Role 사용
dev / stage / prod 가 같은 Role을 공유하면 한쪽 사고가 다른 쪽에 번진다.
3. 좁히는 방법 — Action
먼저 어떤 Action 이 필요한지 구체적으로 적는다.
"Action": [
"s3:GetObject",
"s3:PutObject"
]
s3:* 대신 실제 호출하는 API 만.
4. 좁히는 방법 — Resource
자원도 구체적으로.
"Resource": [
"arn:aws:s3:::msa-uploads/*"
]
* 대신 버킷 이름 · 키 prefix 까지.
DynamoDB:
"Resource": "arn:aws:dynamodb:ap-northeast-2:123:table/orders"
5. 좁히는 방법 — Condition
특정 조건일 때만 허용.
"Condition": {
"StringEquals": { "aws:RequestedRegion": "ap-northeast-2" },
"Bool": { "aws:SecureTransport": "true" }
}
- 특정 리전에서만
- HTTPS 호출만
- 특정 VPC Endpoint 통해서만
- 특정 시간대에만
6. 환경별 분리
prod / stage / dev 의 IAM은 분리한다
방법:
- 별도 AWS 계정 — 가장 깔끔 (Organizations 사용)
- 같은 계정 안에서 Role · 정책 이름 · 자원 prefix 로 분리
prod-orders-task vs dev-orders-task
arn:aws:s3:::msa-prod-uploads vs msa-dev-uploads
7. IAM Access Analyzer — 자동 점검
AWS가 제공하는 분석 도구.
- 외부에 노출된 자원 자동 발견
- 사용되지 않는 권한 식별
- Policy 작성 시 권장 사항 제시
aws accessanalyzer list-findings
정기적으로 검토 — 안 쓰는 권한은 제거
8. 권한 사용 로그 — CloudTrail
CloudTrail은 모든 AWS API 호출 로그를 남긴다.
"이 Role 이 지난 90일 동안 실제로 어떤 Action을 호출했나"
IAM 콘솔에 “Access Advisor” 가 이걸 요약해서 보여준다.
“이 Role이 실제로 쓴 Action 만 남기고 나머지 정리”
운영 정리 작업의 표준.
9. SCP — 조직 차원의 한도
AWS Organizations 환경에서는 Service Control Policy 로 조직 전체에 한도를 건다.
"우리 조직 안의 어떤 계정도 us-east-1 외에는 못 쓴다"
"루트 계정의 일부 권한은 무조건 차단"
"특정 서비스는 사용 금지"
SCP는 Allow 이전에 작동하는 천장이다 — 개별 계정의 IAM이 더 넓어도 SCP가 막는다.
10. 우리 서비스에서
[orders-task Role]
Allow:
rds-db:connect → orders-db / orders-app
sns:Publish → order-events 토픽
secretsmanager:GetSecretValue → orders/db-password
Condition:
aws:RequestedRegion = ap-northeast-2
aws:SecureTransport = true
Deny:
iam:* (절대 IAM 변경 못 함)
각 서비스의 Task Role이 이 모양으로 좁아진다.
11. 직접 확인해보기 — CLI
정책 시뮬레이션
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::123:role/orders-task \
--action-names s3:DeleteObject \
--resource-arns arn:aws:s3:::msa-uploads/x
“이 Role이 이 작업을 할 수 있나” 를 사전 검증.
Access Advisor (콘솔)
IAM → Role → Access Advisor → 마지막 사용 시각
90일 이상 안 쓴 권한은 정리 후보.
12. 코드로는 이렇게 생겼다 — Terraform
data "aws_iam_policy_document" "orders_task" {
statement {
actions = ["rds-db:connect"]
resources = [
"arn:aws:rds-db:ap-northeast-2:${data.aws_caller_identity.current.account_id}:dbuser:${aws_db_instance.orders.resource_id}/orders-app"
]
}
statement {
actions = ["sns:Publish"]
resources = [aws_sns_topic.order_events.arn]
}
statement {
actions = ["secretsmanager:GetSecretValue"]
resources = [aws_secretsmanager_secret.orders_db.arn]
}
statement {
sid = "RegionLock"
effect = "Deny"
actions = ["*"]
resources = ["*"]
condition {
test = "StringNotEquals"
variable = "aws:RequestedRegion"
values = ["ap-northeast-2"]
}
}
}
resource "aws_iam_role_policy" "orders_task" {
role = aws_iam_role.orders_task.id
policy = data.aws_iam_policy_document.orders_task.json
}
좁은 Allow + 명확한 Deny 가 한 묶음.
13. 이렇게 쓰면 망한다 — 안티패턴
안티패턴 1. *:* 정책 그대로 운영
한 번도 좁히지 않은 정책이 사고의 첫 단추가 된다.
안티패턴 2. AdministratorAccess 를 컨테이너에
운영 컨테이너에 IAM · 빌링 권한까지 들어가면 침해 시 회복 불가능.
안티패턴 3. 디버깅용 권한이 영구화
임시로 풀어준 권한은 일정에 잡고 회수한다.
안티패턴 4. 정책을 한 번 만들고 다시 안 본다
서비스가 진화하면 권한도 달라진다 — 분기마다 정리.
14. 한 줄로 정리
최소 권한은 한 번에 완성되지 않는다 —
좁게 시작 → 실제 사용 로그로 다듬기 → 정기 회수의 반복이다
15. 이 장의 핵심 정리
- 기본은 “다 막혀 있고 필요한 것만 연다”.
- Action · Resource · Condition 세 축을 모두 좁힌다.
- 환경(prod/stage/dev) 은 IAM도 분리한다.
- Access Analyzer · Access Advisor · CloudTrail 로 실제 사용을 검증한다.
- SCP는 조직 차원의 천장이다.
- 최소 권한은 분기마다 정리하는 운영 작업이다.